/***************************************************************************
 *                                                                         *
 *                             ConsoleThread.java                          *
 *                            -------------------                          *
 *   date                 : 16. Mai 2003, 18:27                            *
 *   copyright            : (C) 2004 Distributed and Mobile Systems Group  *
 *                              Lehrstuhl fuer Praktische Informatik       *
 *                              Universitaet Bamberg                       *
 *                              http://www.uni-bamberg.de/pi/              *
 *   email                : sven.kaffille@uni-bamberg.de                   *
 *                                                                         *
 *                                                                         *
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   A copy of the license can be found in the license.txt file supplied   *
 *   with this software or at: http://www.gnu.org/copyleft/gpl.html        *
 *                                                                         *
 ***************************************************************************/

package de.uniba.wiai.lspi.util.console;

import java.io.*;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import de.uniba.wiai.lspi.util.console.parser.CommandParser;
import de.uniba.wiai.lspi.util.console.parser.ParseException;
import de.uniba.wiai.lspi.util.console.parser.TokenMgrError;

/**
 * @author sven
 * @version 1.0.5
 */
public class ConsoleThread extends Thread {

	/**
	 * Reference to the only instance of this.
	 */
	static ConsoleThread console;

	/**
	 * The name of this console. Displayed before any input.
	 */
	protected String consoleName;

	/**
	 * The factory responsible for Creation of commands.
	 */
	protected CommandFactory factory;

	/**
	 * The text displayed, when the Thread is started.
	 */
	protected String welcome = "Welcome!";

	/**
	 * The {@link Command} that exits the console. Its execute method is invoked
	 * and the console exits.
	 */
	protected Command exitCommand;

	/**
	 * The PrintStream to print the consoles output to.
	 */
	protected PrintStream out;

	/**
	 * The old PrintStream to print the standard output to. Is <code>null</code>
	 * if this console does not change the standard output via constructor
	 * {@link #ConsoleThread(String, CommandFactory)}.
	 */
	protected PrintStream systemOut = null;

	/**
	 * Reference to the standard output stream.
	 */
	protected OutputStream systemOutputStream = null;

	/**
	 * Creates a new instance of ConsoleThread
	 * 
	 * @param name
	 *            The name of this console. Displayed before any input.
	 * @param f
	 *            The {@link CommandFactory} responsible for creating
	 *            {@link Command}s.
	 */
	protected ConsoleThread(String name, CommandFactory f) {
		super("Console-" + name);
		this.consoleName = name;
		this.factory = f;
		this.out = f.getPrintStream();
	}

	/**
	 * Creates a new instance of ConsoleThread. The standard output
	 * {@link System#out} is redirected to <code>systemOut</code>.
	 * 
	 * @param name
	 *            The name of this console. Displayed before any input.
	 * @param f
	 *            The {@link CommandFactory} responsible for creating
	 *            {@link Command}s.
	 * @param systemOut
	 *            The {@link OutputStream} to redirect standard ouput to. If you
	 *            do not want to have any output send to System.out, you can use
	 *            {@link DummyOutputStream}. If you want to save output send to
	 *            System.out in Memory to e.g. display it later you can use
	 *            {@link MemoryOutputStream}. To write output to a file you can
	 *            use {@link java.io.FileOutputStream}.
	 */
	protected ConsoleThread(String name, CommandFactory f,
			OutputStream systemOut) {
		this(name, f);
		/* save System.out to restore it when this console thread is stopped. */
		this.systemOut = System.out;
		this.systemOutputStream = systemOut;
		System.setOut(new PrintStream(this.systemOutputStream));
	}

	/**
	 * Method to obtain a reference to the console currently active in this JVM.
	 * Returns <code>null</code>, if there is none.
	 * 
	 * @return Reference to the singleton console thread.
	 */
	public static ConsoleThread getConsole() {
		return console;
	}

	/**
	 * Factory method to get a reference to the console singleton. Creates a new
	 * instance if there is no console in the JVM. If there is currently one
	 * console the arguments provided to this method have no effect.
	 * 
	 * @param name
	 * @param factory
	 * @return Reference to the singleton console thread.
	 */
	public static ConsoleThread getConsole(String name, CommandFactory factory) {
		if (console == null) {
			console = new ConsoleThread(name, factory);
		}
		return console;
	}

	/**
	 * Factory method to create a console. Creates a new instance if there is no
	 * console in the JVM. If there is currently one console the arguments
	 * provided to this method have no effect.
	 * 
	 * @param name
	 * @param factory
	 * @param systemOut
	 * @return Reference to the singleton console thread.
	 */
	public static ConsoleThread getConsole(String name, CommandFactory factory,
			OutputStream systemOut) {
		if (console == null) {
			console = new ConsoleThread(name, factory, systemOut);
		}
		return console;
	}

	/**
	 * Get a reference to the {@link PrintStream} this console prints its output
	 * to.
	 * 
	 * @return Reference to the {@link PrintStream} this console prints its
	 *         output to.
	 */
	public PrintStream getPrintStream() {
		return this.out;
	}

	/**
	 * Get a reference to the {@link OutputStream} calls to System.out are
	 * delegated to. Returns <code>null</code> if System.out has not been
	 * redirected.
	 * 
	 * @return Reference to the {@link OutputStream} calls to System.out are
	 *         delegated to. Returns <code>null</code> if System.out has not
	 *         been redirected.
	 */
	public OutputStream getSystemOutputStream() {
		return this.systemOutputStream;
	}

	/**
	 * Get a reference to the {@link CommandFactory} used by this console.
	 * 
	 * @return Reference to the {@link CommandFactory} used by this console.
	 */
	public CommandFactory getCommandFactory() {
		return this.factory;
	}

	/**
	 * Set a costum welcome text for the console.
	 * 
	 * @param text
	 *            The welcome text to set.
	 */
	public void setWelcomeText(String text) {
		this.welcome = text;
	}

	/**
	 * The run method. Loops until the exitCommand has been invoked.
	 */
	public void run() {
		final ExecutorService pool = Executors.newFixedThreadPool(20);
		
		this.out.println(this.welcome);
		boolean running = true;
		this.out.println("Console ready. ");
        int commandIdx = 0;

        String[] commands;
        StringBuilder content = new StringBuilder();
        File outFile = new File("ori_output");
        int numCmds = Integer.parseInt(System
            .getProperty("de.uniba.wiai.lspi.util.console.ConsoleThread.numCmds"));
        int numNodes = Integer.parseInt(System
            .getProperty("de.uniba.wiai.lspi.util.console.ConsoleThread.numNodes"));
        int valueSize = Integer.parseInt(System
            .getProperty("de.uniba.wiai.lspi.util.console.ConsoleThread.valueSize"));
        //int valueSize = 1000;

        try {
             outFile.createNewFile();
        }
        catch(IOException e) {
        }
        
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(outFile);
        }
        catch (IOException e) {
        }
        commands = new String[2 + numCmds];
        commands[0] = "create -names p0";
        commands[1] = "create -names ";
        for(int i = 1; i < numNodes; i++) {
            if(i != (numNodes - 1)) {
                commands[1] += "p" + i + "_";
            }
            else {
                commands[1] += "p" + i;
            }
        }
        commands[1] += " -bootstraps p0";
        Random rand = new Random(System.currentTimeMillis());

        for(int i = 2; i < commands.length; ++i) {
            commands[i] = "insert -node p0 -key " + i + "_fusion_test" + " -value blah";
        }   
        for(int j = 0; j < valueSize; ++j) {
        	content.append("blah_blah_blah_blah_blah_blah_blah_blah_blah_blah_blah_blah");
        }
        long startTime = 0; 
		while (running) {
			//this.out.print(this.consoleName + " > ");
			try {
				//BufferedReader reader = new BufferedReader(
				//		new InputStreamReader(System.in));
				//String command = reader.readLine();
                if(commandIdx >= commands.length) {
            		pool.shutdown();
            		System.err.println(System.currentTimeMillis() - startTime);
                	System.exit(0);
                }
                String command = commands[commandIdx++].trim();
                if(commandIdx == 2) {
                    startTime = System.currentTimeMillis(); 
                }
				//if (command == null) {
				//	command = "";
				//}
				//command = command.trim();
                
                //String str = "start: " + (System.currentTimeMillis() - startTime) + " end: ";
                long cmd_start = System.currentTimeMillis();
                
				if (!command.equals("")) {
					String c = CommandParser.parse(command);
					if ((this.exitCommand != null)
							&& (c.equalsIgnoreCase(this.exitCommand
									.getCommandName()))) {
						assert (false); // this section is disabled for experiment
						/*
						this.out.println("Exiting " + this.consoleName + "...");
						Command com = this.factory.createCommand(command);
						this.out.print("Do you really want to shutdown? ");
						String answer = "";
						while ((answer == null) || (answer.length() == 0)) {
							//try {
							//	answer = reader.readLine();
							//} catch (IOException e) {
							//	answer = ""; 
							//}
							if ( (answer != null) && ((answer.equalsIgnoreCase("Yes"))
									|| (answer.equalsIgnoreCase("Y")))) {
								com.execute();
								running = false;
							}

						}//*/
					} else {
						try {
							if (commandIdx > 2) {
								command += new String(content);
								Command com = this.factory.createCommand(command);
								pool.execute(com);
							} else {
								Command com = this.factory.createCommand(command);
								com.execute();
							}
						} catch (ConsoleException e) {
							this.out.println(e.getMessage());
							// e.printStackTrace(out);
						}
					}
				}
                //str += (System.currentTimeMillis() - startTime) + "\n";
                if(commandIdx > 2) {
                    String str = (System.currentTimeMillis() - startTime) + "\n";
                    fos.write(str.getBytes());
                }
			} catch (TokenMgrError tme) {
				this.out.println("Could not parse command.");
				this.out.println(tme.getMessage());
			} catch (ParseException pe) {
				this.out.println("Could not parse command.");
				this.out.println(pe.getMessage());
			} catch (Throwable t) {
				t.printStackTrace();
				this.out.println("An unexpected Exception occured. Could not execute command. Reason: ");
				this.out.println(t.getMessage());
			}

		}
		this.out.println("Shutting down.");
		/* restore System.out */
		System.setOut(System.out);
	}

	/**
	 * Set the {@link Command} that exits this console. Uses the
	 * {@link CommandFactory} to create an instance of the Command.
	 * 
	 * @param commandName
	 *            The name of the command.
	 * @throws ConsoleException
	 *             Exception during creation of the command.
	 */
	public void setExitCommand(String commandName) throws ConsoleException {
		this.exitCommand = this.factory.createCommand(commandName);
	}

}
